/*
 * Copyright (C) 2023, KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <notification-client.h>
#include <applications-settings.h>
#include <notification-global-settings.h>

#include <QHash>
#include <QVector>
#include <QTimer>
#include <QDebug>

#include <mutex>

#include "notification-model.h"

std::once_flag once_flag;

namespace UkuiNotification {

QHash<int, QByteArray> NotificationItem::roles()
{
    static QHash<int, QByteArray> roles;

    std::call_once(once_flag, [] {
        roles.insert(Id, "id");
        roles.insert(Display, "display");
        roles.insert(AppName, "appName");
        roles.insert(AppIconName, "appIconName");
        roles.insert(Icon, "icon");
        roles.insert(Summary, "summary");
        roles.insert(Body, "body");
        roles.insert(Category, "category");
        roles.insert(Image, "image");
        roles.insert(CreateTime, "createTime");
        roles.insert(Actions, "actions");
        roles.insert(ActionState, "actionState");
        roles.insert(HasDefaultAction, "hasDefaultAction");
        roles.insert(EnableActionIcons, "enableActionIcons");
        roles.insert(SoundFile, "soundFile");
        roles.insert(SuppressSound, "suppressSound");
        roles.insert(Resident, "resident");
        roles.insert(Transient, "transient");
        roles.insert(Urgency, "urgency");
        roles.insert(Timout, "timout");
        roles.insert(NoFold, "noFold");
        roles.insert(PopupTimeout, "popupTimeout");
        roles.insert(IsStored, "isStored");
        roles.insert(GroupName, "groupName");
        roles.insert(GroupCount, "groupCount");
        roles.insert(GroupIndex, "groupIndex");
    });

    return roles;
}

NotificationItem::NotificationItem(const PopupNotification& notification)
{
    setData(notification);
}

uint NotificationItem::id() const
{
    return data.id();
}

// 消息生命
int NotificationItem::timeout() const
{
    if (flag & TransientPopup) {
        // 弹窗时间
        return data.popupTimeout();
//        return /*data.popupTimeout()*/ 6;
    }

    // 销毁时间
    return data.timeout() == 0 ? 1 : data.timeout();
//    return 6;
}

void NotificationItem::setData(const PopupNotification &notification)
{
    data = notification;
    updateActions();
}

void NotificationItem::updateActions()
{
    actions.clear();

    ActionList actionList(data.actions());
    QStringList actionState(data.actionState());

    for (int i = 0; i < actionList.size(); ++i) {
        NotificationAction action;

        action.setEnable(true);
        action.setDefault(data.hasDefaultAction() && actionList[i].first == "default");
        action.setIndex(i);
        action.setIcon(data.enableActionIcons() ? actionList[i].second : "");
        action.setName(actionList[i].second);
        action.setAction(actionList[i].first);
        action.setState(i < actionState.size() ? actionState[i] : "");

        actions.append(QVariant::fromValue(action));
    }
}

// ===== Private ======//
class NotificationModelPrivate
{
public:
    virtual ~NotificationModelPrivate();
    NotificationGlobalSettings *globalSettings {nullptr};
    // 消息客户端，收取消息
    NotificationClient *client {nullptr};

    // 全部消息记录
//    QVector<PopupNotification> notifications;
    QVector<NotificationItem> notifications;
    // 计时器，控制每条弹窗消息定时收起
    QHash<uint, QTimer*> notificationTimers;

    // 即将删除的消息Id列表
    QSet<uint> pendingRemovalIds;
    // 计时器，在计时器结束后，删除消息
    QTimer *pendingRemovalTimer {nullptr};

public:
    void deleteTimer(const uint &id);
    void prepareToDeleteNotification(const uint &id);
};

NotificationModelPrivate::~NotificationModelPrivate()
{
    qDeleteAll(notificationTimers);
    notificationTimers.clear();
}

void NotificationModelPrivate::deleteTimer(const uint &id)
{
    delete notificationTimers.take(id);
}

void NotificationModelPrivate::prepareToDeleteNotification(const uint &id)
{
    // 删除定时器
    deleteTimer(id);

    // 重启计时器，删除通知
    pendingRemovalTimer->stop();
    pendingRemovalIds.insert(id);
    pendingRemovalTimer->start();
}

// ===== M ====== //
NotificationModel *NotificationModel::instance()
{
    static NotificationModel instance;
    return &instance;
}

NotificationModel::NotificationModel(QObject *parent) : QAbstractListModel(parent), d(new NotificationModelPrivate)
{
    qRegisterMetaType<NotificationAction>("NotificationAction");
    init();
}

void NotificationModel::init()
{
    // 初始化timer
    d->pendingRemovalTimer = new QTimer(this);
    d->pendingRemovalTimer->setSingleShot(true);
    d->pendingRemovalTimer->setInterval(50);
    connect(d->pendingRemovalTimer, &QTimer::timeout, this, &NotificationModel::removeNotifications);

    // 通知全局设置
    d->globalSettings = new NotificationGlobalSettings(this);

    d->client = new NotificationClient(this);

    connect(d->client, &NotificationClient::newNotification, this, &NotificationModel::onNotificationReceived);
    connect(d->client, &NotificationClient::notificationClosed, this, &NotificationModel::onNotificationClosed);

    if (!d->client->registerClient()) {
        qWarning() << "register client failed.";
    }
}

int NotificationModel::findNotificationIndex(uint id) const
{
    auto it = std::find_if(d->notifications.constBegin(), d->notifications.constEnd(), [&id] (const NotificationItem &n) {
        return n.id() == id;
    });

    if (it == d->notifications.constEnd()) {
        return -1;
    }

    return std::distance(d->notifications.constBegin(), it);
}

void NotificationModel::onNotificationReceived(const PopupNotification &notification)
{
    SingleApplicationSettings *appSetting = ApplicationsSettings::self()->creatSettings(notification);
    if (!appSetting->allowNotify()) {
        return;
    }

    int index = findNotificationIndex(notification.id());
    if (index >= 0) {
        // 已经存在消息，更新消息
        updateNotification(index, notification);
        return;
    }

    // TODO 查询应用设置，检查系统设置，响铃

    /*
     *   bool allowNotify() const;  y
     *   bool allowSound() const ;  y
     *   bool showContentOnLockScreen() const ;  n
     *   bool showNotificationOnLockScreen() const;  n
     *   SettingsProperty::Property popupStyle() const;  y
     */

    NotificationItem item(notification);

    // 勿扰模式
    if (d->globalSettings->isDND()) {
        item.flag = NotificationItem::Stored;

    } else {
        switch (appSetting->popupStyle()) {
            case SettingsProperty::TransientPopup:
                item.flag = NotificationItem::TransientPopup;
                break;
            case SettingsProperty::ResidentPopup:
                item.flag = NotificationItem::ResidentPopup;
                break;
            case SettingsProperty::NoPopup:
            default:
                item.flag = NotificationItem::Stored;
        }

        if (appSetting->allowSound()) {
            // 响铃 ding ~
        }
    }

    if (item.timeout() > 0) {
        updateNotificationTimer(item.id(), item.timeout(), (item.flag & ~NotificationItem::TransientPopup));
    }

    // 插入数据
    beginInsertRows(QModelIndex(), d->notifications.size(), d->notifications.size());
    d->notifications.append(std::move(item));
    endInsertRows();
}

void
NotificationModel::onNotificationClosed(uint id, UkuiNotification::NotificationCloseReason::CloseReason closeReason)
{
    int index = findNotificationIndex(id);
    if (index < 0) {
        return;
    }

    // 插入数据
    beginRemoveRows(QModelIndex(), index, index);
    d->notifications.removeAt(index);
    endRemoveRows();
}

void NotificationModel::updateNotification(int row, const PopupNotification &notification)
{
    NotificationItem &item = d->notifications[row];
    item.setData(notification);

    if (d->pendingRemovalIds.contains(item.id())) {
        d->pendingRemovalIds.remove(item.id());
    }

    Q_EMIT dataChanged(index(row), index(row));
}

/**
 * 初始化某条消息的timer,
 * 如果已经存在timer，那就重新启动timer，将消息收起的时间延长
 * timer计时结束后，将允许收起的消息收起
 * @param id 消息id
 * @param timeout 超时时间，单位：秒
 * @param destroy 在定时结束后，是否删除消息
 */
void NotificationModel::updateNotificationTimer(uint id, int timeout, bool destroy)
{
    QTimer *timer = d->notificationTimers.value(id, nullptr);
    if (!timer) {
        timer = new QTimer();
        timer->setSingleShot(true);
        timer->setProperty("id", id);

        // timer的两种状态:
        // 1.作为弹窗计时器 (计时结束后收起弹窗)
        // 2.作为消息生命周期计时器 (计时结束后删除消息)
        connect(timer, &QTimer::timeout, this, [this, timer] {
            uint id = timer->property("id").toUInt();
            bool destroy = timer->property("destroy").toBool();
            // 删除消息
            if (destroy) {
                d->prepareToDeleteNotification(id);
            } else {
                storeNotification(id);
            }
        });

        d->notificationTimers.insert(id, timer);
    }

    timer->stop();
    timer->setProperty("destroy", destroy);
    timer->setInterval(timeout * 1000);
    timer->start();
}

/**
 * 定时器结束后，调用该函数
 * @param id
 */
void NotificationModel::storeNotification(uint id)
{
    int idx = findNotificationIndex(id);
    if (idx < 0) {
        d->deleteTimer(id);
        return;
    }

    NotificationItem &item = d->notifications[idx];
    item.flag = NotificationItem::Stored;

    if (item.timeout() > 0) {
        updateNotificationTimer(id, item.timeout(), true);
        return;
    }

    d->deleteTimer(id);
    Q_EMIT dataChanged(index(idx), index(idx));
}

void NotificationModel::removeNotifications()
{
    if (d->pendingRemovalIds.isEmpty()) {
        return;
    }

    QVector<int> rows;
    rows.reserve(d->pendingRemovalIds.size());

    for (uint id : d->pendingRemovalIds) {
        int row = findNotificationIndex(id);
        if (row < 0) {
            continue;
        }
        rows.append(row);
    }

    if (rows.isEmpty()) {
        d->pendingRemovalIds.clear();
        return;
    }

    std::sort(rows.begin(), rows.end());

    // 准备删除row数据
    QVector<QPair<int, int>> rangeList;
    QPair<int, int> range {rows[0], rows[0]};

    for (int row : rows) {
        if (row > (range.second + 1)) {
            rangeList.append(range);
            range.first = row;
            continue;
        }

        range.second = row;
    }

    if (rangeList.isEmpty() || (rangeList.last() != range)) {
        rangeList.append(range);
    }

    int removedRows = 0;
    for (int i = rangeList.count() - 1; i >= 0; --i) {
        range = rangeList[i];
        beginRemoveRows(QModelIndex(), range.first, range.second);
        for (int j = range.second; j >= range.first; --j) {
            // close notification
            d->client->closeNotification(d->notifications[j].id(), d->notifications[j].closeReason);
            d->notifications.removeAt(j);
            ++removedRows;
        }
        endRemoveRows();
    }

//    qDebug() << "=removedRows=" << removedRows;
    d->pendingRemovalIds.clear();
}


// ====== !!! Model oh ~ oh ~ oh ~ oh ~ oh ~ ======
int NotificationModel::rowCount(const QModelIndex &parent) const
{
    return d->notifications.count();
}

QVariant NotificationModel::data(const QModelIndex &index, int role) const
{
    if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        return {};
    }

    const NotificationItem &item = d->notifications[index.row()];
    switch (role) {
        case NotificationItem::Id:
            return item.id();
        case NotificationItem::Display:
            return item.data.display();
        case NotificationItem::AppName:
            return item.data.applicationName();
        case NotificationItem::AppIconName:
            return item.data.applicationIconName();
        case NotificationItem::Icon:
            return item.data.icon();
        case NotificationItem::Summary:
            return item.data.summary();
        case NotificationItem::Body: {
            return item.data.body();
        }
        case NotificationItem::Category:
            return item.data.category();
        case NotificationItem::Image:
            return item.data.image();
        case NotificationItem::CreateTime:
            return item.data.createdTime();
        case NotificationItem::Actions:
            return QVariant::fromValue(item.actions);
        case NotificationItem::EnableActionIcons:
            return item.data.enableActionIcons();
        case NotificationItem::SoundFile:
            return item.data.soundFile();
        case NotificationItem::SuppressSound:
            return item.data.suppressSound();
        case NotificationItem::Resident:
            return item.data.resident();
        case NotificationItem::Transient:
            return item.data.transient();
        case NotificationItem::Urgency:
            return item.data.urgency();
        case NotificationItem::Timout:
            return item.data.timeout();
        case NotificationItem::NoFold:
            return item.data.noFold();
        case NotificationItem::PopupTimeout:
            return item.data.popupTimeout();
        case NotificationItem::IsStored: {
            return item.flag & NotificationItem::Stored;
        }
        default:
            break;
    }

    return {};
}

QHash<int, QByteArray> NotificationModel::roleNames() const
{
    return NotificationItem::roles();
}

void NotificationModel::closeNotification(uint id)
{
    int index = findNotificationIndex(id);
    if (index < 0) {
        return;
    }

    d->notifications[index].closeReason = NotificationCloseReason::DismissedByUser;
    d->prepareToDeleteNotification(id);
}

void NotificationModel::execAction(uint id, int actionIndex)
{
    int index = findNotificationIndex(id);
    if (index < 0) {
        return;
    }

    QString action;
    NotificationItem &item = d->notifications[index];

    if (actionIndex < 0) {
        if (item.data.hasDefaultAction()) {
            action = "default";
        }

    } else if (actionIndex < item.actions.size()) {
        action = item.actions[actionIndex].value<NotificationAction>().action();
    }

    if (action.isEmpty()) {
        return;
    }

    qDebug() << "=exec action=" << action;

    d->client->invokeAction(id, action);
    if (!item.data.resident()) {
        item.closeReason = NotificationCloseReason::Revoked;
        d->prepareToDeleteNotification(id);
    }
}


} // Notification
